55  绘制直方图

列表 55.1
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC', 'SimHei', 'Microsoft YaHei']  # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

55.1 引言分布的可视化

直方图(Histogram)是展示数据分布最直观的工具。在金融分析中,直方图帮助我们: - 理解分布形态:数据是对称、偏斜还是多峰 - 识别异常值:远离主分布的孤立值 - 评估风险:分布的尾部形态决定极端事件概率 - 比较群体:不同资产、不同时期的分布差异

55.2 直方图的数学原理

直方图概率密度函数(PDF)的离散近似:

  1. 分箱(Binning):将数据范围分成 \(k\) 个区间(bin)
  2. 计数(Counting):统计每个区间内的数据点数
  3. 归一化(Normalization):转换为概率或密度

数学定义:

设数据集 \(X = \{x_1, x_2, \ldots, x_n\}\),分箱边界为 \(b_0, b_1, \ldots, b_k\):

\[ \text{count}_i = |\{x_j | b_{i-1} \leq x_j < b_i\}| \]

频率(Frequency): \[ f_i = \frac{\text{count}_i}{n} \]

密度(Density): \[ d_i = \frac{f_i}{b_i - b_{i-1}} = \frac{\text{count}_i}{n \cdot (b_i - b_{i-1})} \]

55.3 基础直方图

平台任务1解答代码

以下代码与教学平台任务要求完全一致:

列表 55.2
# 注:平台任务代码,依赖平台指定的外部数据URL
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)

stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率

stock_return = stock_return.dropna()     #删除缺失值所在的行

stock_return.describe()  # 查看stock_return的描述性统计量

stock_return = stock_return.dropna()         #删除缺失值所在的行

print(stock_return)  # 输出股票数据

平台任务3解答代码

以下代码与教学平台任务要求完全一致:

列表 55.3
# 注:平台任务代码,依赖平台指定的外部数据URL及tkinter模块
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
from tkinter import font  # 导入font模块
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
plt.rcParams['axes.unicode_minus']=False  # 设置Matplotlib全局参数

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)
stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率
stock_return = stock_return.dropna()     #删除缺失值所在的行
SHI_return = np.array(stock_return.iloc[:,2:]) #将上海石化A股和美股日收益率转为数组形式
plt.figure(figsize=(9,10))  # 创建图形画布
plt.subplot(2,1,1)  # 选择子图位置
plt.hist(SHI_return,label=[u"上海石化A股日收益率",u"上海石化美股日收益率"],stacked=True,edgecolor="k",bins=30)  #以堆叠形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.subplot(2,1,2)  # 选择子图位置
plt.hist(SHI_return,label=[u"上海石化A股日收益率",u"上海石化美股日收益率"],edgecolor="k",bins=30) #以并排形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.xlabel(u"股票日收益率",fontsize=13)  # 设置X轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.savefig("2.png")  # 保存图形至文件

平台任务4解答代码

以下代码与教学平台任务要求完全一致:

列表 55.4
# 注:平台任务代码,依赖平台指定的外部数据URL
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
plt.rcParams['axes.unicode_minus']=False  # 设置Matplotlib全局参数

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)
stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率
stock_return = stock_return.dropna()     #删除缺失值所在的行
CEA_return = np.array(stock_return.iloc[:,0:2]) #将东方航空A股和美股收益率转为数组形式
plt.figure(figsize=(9,10))  # 创建图形画布
plt.subplot(2,1,1)  # 选择子图位置
plt.hist(CEA_return,label=[u"东方航空A股日收益率",u"东方航空美股日收益率"],stacked=True,edgecolor="k",bins=30)  #以堆叠形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.subplot(2,1,2)  # 选择子图位置
plt.hist(CEA_return,label=[u"东方航空A股日收益率",u"东方航空美股日收益率"],edgecolor="k",bins=30) #以并排形式展出
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylabel(u"频数",fontsize=13,rotation=90)  # 设置Y轴标签
plt.xlabel(u"股票日收益率",fontsize=13)  # 设置X轴标签
plt.legend(fontsize=13)  # 添加图例
plt.grid()  # 显示网格线
plt.savefig("3.png")  # 保存图形至文件
列表 55.5
# 注:平台任务代码,依赖平台指定的外部数据URL
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
plt.rcParams['axes.unicode_minus']=False  # 设置Matplotlib全局参数

# 从Excel文件读取数据存入stock_price
stock_price = pd.read_excel("https://huoran.oss-cn-shenzhen.aliyuncs.com/20220824/xlsx/1562273067622227968.xlsx",sheet_name="Sheet1",header=0,index_col=0)
stock_price = stock_price.dropna()   #删除缺失值所在的行

stock_return = np.log(stock_price/stock_price.shift(1))  #计算股票收益率
stock_return = stock_return.dropna()     #删除缺失值所在的行
plt.figure(figsize=(9,9))  # 创建图形画布
plt.subplot(2,1,1)  # 选择子图位置
plt.plot(stock_return["东方航空(A股)"],"r-",label=u"东方航空(A股)",lw=2)  # 绑制折线图
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylim(-0.12,0.15)  # 设置Y轴显示范围
plt.ylabel(u"收益率",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(loc=9,fontsize=13)     #图例放在中上位置
plt.grid()  #加入网格线
plt.subplot(2,1,2) #代表第二行的子图
plt.plot(stock_return["东方航空(美股)"],"b-",label=u"东方航空(美股)",lw=2)  # 绑制折线图
plt.xticks(fontsize=13)  # 设置X轴刻度标签
plt.xlabel(u"日期",fontsize=13)  # 设置X轴标签
plt.yticks(fontsize=13)  # 设置Y轴刻度标签
plt.ylim(-0.12,0.15)  # 设置Y轴显示范围
plt.ylabel(u"收益率",fontsize=13,rotation=90)  # 设置Y轴标签
plt.legend(loc=9,fontsize=13)     #图例放在中上位置
plt.grid()  #加入网格线
plt.show()  # 显示图形
plt.savefig("1.png")  # 保存图形至文件

关键参数解析:

  1. bins:箱数 -太少:信息损失,分布细节丢失 -太多:过度拟合,噪声干扰

    • 经验法则:\(k = \sqrt{n}\)\(k = \log_2(n)\)
  2. edgecolor:箱边颜色,'white'使分箱更清晰

  3. alpha:透明度,0.7使图表不显沉重

55.4 概率密度与直方图

列表 55.6
# =============================================================================
# 题目:概率密度曲线与直方图叠加
# =============================================================================
# 本任务演示如何在直方图上叠加理论概率密度曲线,检验分布拟合
# 应用场景:正态性检验、模型验证、风险评估

# ==================== 生成正态分布数据 ====================
mu, sigma = 0.001, 0.02  # mu:均值,sigma:标准差
data = np.random.normal(mu, sigma, 1000)

# ==================== 绘制直方图(密度) ====================
plt.figure(figsize=(10, 6))

# plt.hist()绘制直方图
# density=True:将Y轴归一化为概率密度(而非频数)
# 这样直方图的总面积为1,可以与概率密度函数对比
n, bins, patches = plt.hist(data, bins=30, density=True,
                            color='#2E86AB', alpha=0.7,
                            edgecolor='white', label='样本分布')
# n:每个区间的密度值
# bins:区间的边界
# patches:每个柱子的图形对象

# ==================== 叠加理论正态分布曲线 ====================
# np.linspace()生成等间距的序列
# data.min():起始值(数据最小值)
# data.max():结束值(数据最大值)
# 100:生成100个点
x = np.linspace(data.min(), data.max(), 100)

# 计算正态分布的概率密度函数(PDF)
# (1 / (sigma * np.sqrt(2 * np.pi))):归一化常数
# np.exp(-0.5 * ((x - mu) / sigma)**2):指数部分
pdf = (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mu) / sigma)**2)

# plt.plot()绘制理论正态分布曲线
# linewidth=2.5:线宽为2.5(比普通线条粗,突出显示)
plt.plot(x, pdf, color='#E3120B', linewidth=2.5,
         label='理论正态分布')

# ==================== 设置图表装饰 ====================
plt.title('收益率分布与正态拟合', fontsize=16, fontweight='bold')
plt.xlabel('收益率', fontsize=12)
plt.ylabel('概率密度', fontsize=12)
plt.legend(fontsize=11)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

# ==================== 计算拟合优度 ====================
# from scipy import stats:导入SciPy的统计模块
from scipy import stats

# stats.kstest()执行Kolmogorov-Smirnov检验
# 比较经验分布与理论分布的差异
# data:样本数据
# 'norm':理论分布类型(正态分布)
# args=(mu, sigma):理论分布的参数(均值、标准差)
ks_stat, ks_pvalue = stats.kstest(data, 'norm', args=(mu, sigma))
print(f'Kolmogorov-Smirnov检验:')
print(f'统计量: {ks_stat:.4f}')
print(f'p值: {ks_pvalue:.4f}')
# p值 > 0.05:无法拒绝正态假设(数据可能服从正态分布)
# p值 ≤ 0.05:拒绝正态假设(数据不服从正态分布)
print(f'结论: {"无法拒绝正态假设" if ks_pvalue > 0.05 else "拒绝正态假设"}')

正态分布的数学形式:

\[ f(x) = \frac{1}{\sigma\sqrt{2\pi}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right) \]

其中: - \(\mu\):均值(位置参数) - \(\sigma\):标准差(尺度参数)

拟合检验: - KS检验:比较经验分布与理论分布 - p值 > 0.05:无法拒绝正态假设 - p值 ≤ 0.05:拒绝正态假设

55.5 分组直方图对比

列表 55.7
# =============================================================================
# 题目:分组直方图——不同股票收益率对比
# =============================================================================
# 本任务演示如何使用子图对比多组数据的分布
# 应用场景:对比不同资产的波动率、风险分析

# ==================== 生成三只股票的收益率数据 ====================
stocks = {
    # 贵州茅台:低波动率股票
    # 均值0.001(0.1%),标准差0.018(1.8%)
    '贵州茅台': np.random.normal(0.001, 0.018, 500),
    # 中国平安:中等波动率股票
    # 均值0.0008(0.08%),标准差0.025(2.5%)
    '中国平安': np.random.normal(0.0008, 0.025, 500),
    # 中小板指:高波动率股票
    # 均值0.0015(0.15%),标准差0.035(3.5%)
    '中小板指': np.random.normal(0.0015, 0.035, 500)
}

# ==================== 创建子图 ====================
# plt.subplots(1, 3, figsize=(15, 5))创建1行3列的子图布局
# fig:图形对象
# axes:包含3个子图对象的数组
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

# 定义颜色列表(红、青、深灰)
colors = ['#E3120B', '#008080', '#2C3E50']

# ==================== 循环绘制每个股票的直方图 ====================
# zip(axes, stocks.items(), colors):同时遍历子图、数据、颜色
for ax, (stock, data), color in zip(axes, stocks.items(), colors):
    # ax.hist()在子图上绘制直方图
    # bins=25:分成25个区间
    # color=color:使用对应颜色
    # alpha=0.7:透明度
    # edgecolor='white':白色边框
    ax.hist(data, bins=25, color=color, alpha=0.7, edgecolor='white')

    # ax.axvline()在子图上绘制均值线
    # data.mean():计算该股票的均值收益率
    # color='black':黑色线条
    # linestyle='--':虚线
    # linewidth=2:线宽
    ax.axvline(data.mean(), color='black', linestyle='--', linewidth=2)

    # ax.set_title()设置子图标题
    # f'{stock}\nμ={data.mean():.4f}, σ={data.std():.4f}':
    # 显示股票名称、均值、标准差
    ax.set_title(f'{stock}\nμ={data.mean():.4f}, σ={data.std():.4f}',
                 fontsize=12, fontweight='bold')

    ax.set_xlabel('收益率', fontsize=10)
    ax.set_ylabel('频数', fontsize=10)
    ax.grid(axis='y', alpha=0.3)

# plt.tight_layout()自动调整子图间距
plt.tight_layout()
plt.show()

# ==================== 计算年化波动率 ====================
print('各股票波动率比较:')
for stock, data in stocks.items():
    # 年化波动率 = 日波动率 * sqrt(252)
    # 252:一年的交易日数量
    annual_volatility = data.std() * np.sqrt(252)
    print(f'{stock}: 年化波动率 = {annual_volatility:.2%}')

可视化策略:

方案1:子图对比(上图) - 优点:保持每个分布的完整性 - 缺点:难以直接比较

方案2:叠加直方图:

plt.hist(data1, bins=30, alpha=0.5, label='股票1')
plt.hist(data2, bins=30, alpha=0.5, label='股票2')
  • 优点:直观对比
  • 缺点:颜色叠加可能混淆

方案3:核密度估计(KDE): - 更平滑的分布曲线 - 适合多组对比

55.6 累积直方图

列表 55.8
# =============================================================================
# 题目:累积直方图——收益率分位数
# =============================================================================
# 本任务演示如何绘制累积直方图,展示分位数信息
# 应用场景:VaR计算、收益排序、概率评估

# ==================== 生成数据 ====================
returns = np.random.normal(0.001, 0.02, 1000)

# ==================== 绘制累积直方图 ====================
plt.figure(figsize=(10, 6))

# plt.hist()绘制累积直方图
# cumulative=True:绘制累积分布(而非频数分布)
# density=True:归一化为概率(累积概率)
# returns:收益率数据
# bins=30:分成30个区间
plt.hist(returns, bins=30, cumulative=True, density=True,
         color='#008080', alpha=0.7, edgecolor='white',
         label='累积分布')

# ==================== 标注关键分位数 ====================
# 定义要标注的分位数:5%、25%、50%、75%、95%
percentiles = [5, 25, 50, 75, 95]
for p in percentiles:
    # np.percentile()计算分位数
    # returns:数据
    # p:分位数百分比
    value = np.percentile(returns, p)

    # plt.axvline()绘制垂直线标注分位数
    # value:分位数的值
    # color='#E3120B':红色
    # linestyle=':':点线
    # alpha=0.6:透明度0.6
    plt.axvline(value, color='#E3120B', linestyle=':', alpha=0.6)

    # plt.text()在图上添加文字标签
    # value, 0.5:文字位置(x坐标为分位数值,y坐标为0.5)
    # f'P{p}':文字内容(如P5表示第5百分位)
    # fontsize=9:字号为9
    # rotation=90:文字旋转90度(垂直显示)
    plt.text(value, 0.5, f'P{p}', fontsize=9, rotation=90)

# ==================== 设置图表装饰 ====================
plt.title('收益率累积分布', fontsize=16, fontweight='bold')
plt.xlabel('收益率', fontsize=12)
plt.ylabel('累积概率', fontsize=12)
plt.legend(fontsize=11)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

# ==================== 输出分位数分析 ====================
print('分位数分析:')
for p in [5, 25, 50, 75, 95]:
    value = np.percentile(returns, p)
    # P5:有5%的概率低于此值(95% VaR)
    # P25:第一四分位数
    # P50:中位数
    # P75:第三四分位数
    # P95:有95%的概率低于此值
    print(f'P{p}: {value:.4f}')

累积分布函数(CDF):

\[ F(x) = P(X \leq x) = \int_{-\infty}^x f(t) dt \]

金融应用: 1. VaR计算:VaR at 95% = \(F^{-1}(0.05)\) 2. 收益排序:快速定位表现最好的p%时间段 3. 比较分布:两个CDF图可以清晰看出差异

55.7 二维直方图

列表 55.9
# =============================================================================
# 题目:二维直方图——两资产收益率联合分布
# =============================================================================
# 本任务演示如何绘制二维直方图,展示两个变量的联合分布
# 应用场景:相关性分析、风险分散化效果评估

# ==================== 生成相关的二维数据 ====================
# mean:两个资产的均值(股票A和股票B)
mean = [0.001, 0.0008]

# cov:协方差矩阵(2x2矩阵)
# [[0.0004, 0.0002], [0.0002, 0.0006]]:
#   - 对角线:两个资产各自的方差
#   - 非对角线:两个资产的协方差(相同,对称矩阵)
cov = [[0.0004, 0.0002], [0.0002, 0.0006]]

# np.random.multivariate_normal()生成多元正态分布随机数
# mean:均值向量
# cov:协方差矩阵
# 1000:生成1000个数据点
data_2d = np.random.multivariate_normal(mean, cov, 1000)

x = data_2d[:, 0]  # 股票A收益率(第一列)
y = data_2d[:, 1]  # 股票B收益率(第二列)

# ==================== 绘制二维直方图 ====================
plt.figure(figsize=(10, 8))

# plt.hist2d()绘制二维直方图
# x, y:两个变量的数据
# bins=30:每个维度分成30个区间(共30x30=900个格子)
# cmap='Blues':颜色映射(蓝色渐变,颜色越深表示频数越高)
plt.hist2d(x, y, bins=30, cmap='Blues')

# plt.colorbar()添加颜色条
# label='频数':颜色条的标签
plt.colorbar(label='频数')

# ==================== 添加均值线 ====================
# plt.axvline()绘制垂直线(股票A的均值)
plt.axvline(x.mean(), color='red', linestyle='--', linewidth=2, alpha=0.7)

# plt.axhline()绘制水平线(股票B的均值)
plt.axhline(y.mean(), color='red', linestyle='--', linewidth=2, alpha=0.7)

# ==================== 设置图表装饰 ====================
plt.title('两资产收益率联合分布', fontsize=16, fontweight='bold')
plt.xlabel('股票A收益率', fontsize=12)
plt.ylabel('股票B收益率', fontsize=12)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

# ==================== 计算相关系数 ====================
# np.corrcoef()计算相关系数矩阵
# [0, 1]:取相关系数矩阵的第0行第1列(即两个变量之间的相关系数)
correlation = np.corrcoef(x, y)[0, 1]
print(f'相关系数: {correlation:.4f}')

二维分布的数学含义:

联合概率密度: \[ f_{X,Y}(x, y) \]

相关系数: \[ \rho_{XY} = \frac{\text{Cov}(X, Y)}{\sigma_X \sigma_Y} \]

金融应用: - 分散化效果:相关系数越接近0,分散化效果越好 - 对冲策略:负相关的资产可以相互对冲 - 因子暴露:多资产对共同因子的敏感度

55.8 核密度估计(KDE)

列表 55.10
# =============================================================================
# 题目:核密度估计——平滑分布曲线
# =============================================================================
# 本任务演示如何使用核密度估计(KDE)平滑数据分布
# 应用场景:平滑分布曲线、非参数估计、数据分布探索

# ==================== 生成偏态分布数据 ====================
# np.random.lognormal()生成对数正态分布(右偏分布)
# 0:均值(对数尺度)
# 0.5:标准差(对数尺度)
# 1000:生成1000个数据点
data_skewed = np.random.lognormal(0, 0.5, 1000)

# ==================== 绘制直方图和KDE ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# ==================== 左:直方图 ====================
# axes[0].hist()在第一个子图绘制直方图
# density=True:归一化为概率密度
axes[0].hist(data_skewed, bins=30, density=True,
             color='#2E86AB', alpha=0.7, edgecolor='white')
axes[0].set_title('直方图(离散)', fontsize=14, fontweight='bold')
axes[0].set_xlabel('数值', fontsize=12)
axes[0].set_ylabel('密度', fontsize=12)
axes[0].grid(axis='y', alpha=0.3)

# ==================== 右:KDE ====================
# from scipy.stats import gaussian_kde:导入高斯核密度估计
from scipy.stats import gaussian_kde

# gaussian_kde()创建核密度估计对象
# data_skewed:输入数据
kde = gaussian_kde(data_skewed)

# np.linspace()生成等间距序列(用于绘制平滑曲线)
# data_skewed.min():起始值
# data_skewed.max():结束值
# 500:生成500个点
x_range = np.linspace(data_skewed.min(), data_skewed.max(), 500)

# axes[1].plot()绘制KDE曲线
# kde(x_range):计算每个点的核密度估计值
# linewidth=2.5:线宽
axes[1].plot(x_range, kde(x_range), color='#E3120B', linewidth=2.5)

# axes[1].fill_between()填充曲线下方区域
# kde(x_range):Y轴数据
# alpha=0.3:透明度
axes[1].fill_between(x_range, kde(x_range), alpha=0.3, color='#E3120B')
axes[1].set_title('核密度估计(连续)', fontsize=14, fontweight='bold')
axes[1].set_xlabel('数值', fontsize=12)
axes[1].set_ylabel('密度', fontsize=12)
axes[1].grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()

# ==================== 计算偏度 ====================
print('偏度分析:')
# pd.Series(data_skewed).skew():计算偏度
# 偏度 > 0:右偏(正偏),有较长右尾
skewness_val = pd.Series(data_skewed).skew()
print(f'偏度: {skewness_val:.4f}')
print(f'结论: {"右偏分布" if skewness_val > 0 else "左偏分布" if skewness_val < 0 else "对称分布"}')

核密度估计的数学原理:

\[ \hat{f}_h(x) = \frac{1}{nh}\sum_{i=1}^n K\left(\frac{x-x_i}{h}\right) \]

其中: - \(K(\cdot)\):核函数(常用高斯核) - \(h\):带宽(bandwidth),控制平滑程度

带宽选择: - \(h\) 太小:过度拟合,噪声干扰 - \(h\) 太大:过度平滑,细节丢失 - 经验法则:Silverman’s rule: \(h = 1.06\sigma n^{-1/5}\)

55.9 金融应用正态性检验

列表 55.11
# =============================================================================
# 题目:金融收益率的正态性检验
# =============================================================================
# 本任务演示如何检验收益率数据是否服从正态分布
# 应用场景:模型假设检验、风险评估、衍生品定价

# ==================== 生成真实股票收益率的典型特征(厚尾、负偏) ====================
np.random.seed(42)
n = 1000

# normal_sample:正态分布样本
# 均值为0,标准差为0.02
normal_sample = np.random.normal(0, 0.02, n)

# 添加厚尾和偏态
# np.random.standard_t(3, n):生成自由度为3的t分布(厚尾分布)
# * 0.01:缩放因子
financial_returns = normal_sample + np.random.standard_t(3, n) * 0.01

# ==================== 绘制Q-Q图(分位数-分位数图) ====================
from scipy import stats

# plt.subplots(1, 2, figsize=(14, 6))创建1行2列的子图布局
fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# ==================== 左:直方图 ====================
axes[0].hist(financial_returns, bins=40, density=True,
             color='#2E86AB', alpha=0.7, edgecolor='white',
             label='实际分布')

# 叠加正态分布
x = np.linspace(financial_returns.min(), financial_returns.max(), 100)
# stats.norm.pdf()计算正态分布的概率密度函数
# x:x坐标
# 0:均值
# 0.02:标准差
theoretical_norm = stats.norm.pdf(x, 0, 0.02)
axes[0].plot(x, theoretical_norm, 'r-', linewidth=2,
             label='理论正态')
axes[0].set_title('实际分布 vs 正态分布', fontsize=14, fontweight='bold')
axes[0].set_xlabel('收益率', fontsize=12)
axes[0].set_ylabel('密度', fontsize=12)
axes[0].legend()
axes[0].grid(axis='y', alpha=0.3)

# ==================== 右:Q-Q图 ====================
# stats.probplot()绘制概率图(Q-Q图)
# financial_returns:样本数据
# dist='norm':理论分布类型(正态分布)
# plot=axes[1]:在第二个子图上绘制
stats.probplot(financial_returns, dist='norm', plot=axes[1])
axes[1].set_title('Q-Q图(正态性检验)', fontsize=14, fontweight='bold')
axes[1].grid(alpha=0.3)

plt.tight_layout()
plt.show()

# ==================== 正态性检验 ====================
print('正态性检验结果:')
# pd.Series(financial_returns).skew():计算偏度
print(f'偏度: {pd.Series(financial_returns).skew():.4f}')
# pd.Series(financial_returns).kurtosis():计算峰度
print(f'峰度: {pd.Series(financial_returns).kurtosis():.4f}')

# ==================== Shapiro-Wilk检验 ====================
# stats.shapiro()执行Shapiro-Wilk正态性检验
# 适用于小样本(n < 5000)
# [:5000]:只取前5000个数据点(限制样本量)
shapiro_stat, shapiro_p = stats.shapiro(financial_returns[:5000])
print(f'\nShapiro-Wilk检验:')
print(f'统计量: {shapiro_stat:.4f}')
print(f'p值: {shapiro_p:.4f}')

# ==================== Jarque-Bera检验 ====================
# stats.jarque_bera()执行Jarque-Bera正态性检验
# 基于偏度和峰度
jb_stat, jb_p = stats.jarque_bera(financial_returns)
print(f'\nJarque-Bera检验:')
print(f'统计量: {jb_stat:.4f}')
print(f'p值: {jb_p:.4f}')
# p值 < 0.05:拒绝正态假设
print(f'结论: {"拒绝正态假设" if jb_p < 0.05 else "无法拒绝正态假设"}')

Q-Q图原理:

如果数据服从正态分布,在Q-Q图上: - 散点应落在红色直线上 - 偏离直线表示非正态

偏离模式: - 尾部向上翘起:厚尾分布(金融数据典型特征) - 左端向下:左偏(更多负的极端值) - 右端向上:右偏(更多正的极端值)

金融意义: - 厚尾:极端事件概率高于正态预测 - VaR低估:正态假设会低估在险价值 - 黑天鹅:需要考虑非正态分布的风险模型